package common

import (
	"sort"

	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/contract/native/manage/common"
	protosconfig "hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/store/mq"
)

var (
	stepConList   []*StepConsensus
	stepParamList []*protosconfig.StepSysParam
)

// StepConsensus 共识切换结构定义
type StepConsensus struct {
	StartHeight   uint64
	Txid          []byte
	ConsensusImpl ConsensusModule
	ConParam      *protosconfig.ConsensusParam
}

// GetLastCon 获取最后一个待切换共识
func (bc *BaseClient) GetLastCon() *StepConsensus {
	bc.stepConMutex.RLock()
	defer bc.stepConMutex.RUnlock()
	if len(stepConList) > 1 {
		return stepConList[len(stepConList)-1]
	}
	return nil
}

// GetConByHeight 获取第一个未激活共识
func (bc *BaseClient) GetConByHeight(currentHeight uint64) *StepConsensus {
	bc.stepConMutex.RLock()
	defer bc.stepConMutex.RUnlock()
	for index, con := range stepConList {
		bc.log.Infof("current height %d and con height %d", currentHeight, con.StartHeight)
		if currentHeight == con.StartHeight {
			return stepConList[index]
		}
		continue
	}
	return nil
}

// addConsensusList 向共识列表中添加共识
func (bc *BaseClient) addConsensusList(stepConParam *protosconfig.StepConParam, txid []byte) error {
	var stepCon = &StepConsensus{
		StartHeight:   stepConParam.GetEffectiveHeight(),
		Txid:          txid,
		ConsensusImpl: nil,
		ConParam:      stepConParam.ConParam,
	}
	bc.stepConMutex.Lock()
	defer bc.stepConMutex.Unlock()
	stepConList = append(stepConList, stepCon)
	sort.Slice(stepConList, func(i, j int) bool {
		return stepConList[i].StartHeight < stepConList[j].StartHeight
	})
	return nil
}

func (bc *BaseClient) availableAddConList(effectiveHeight uint64) bool {
	bc.stepConMutex.RLock()
	defer bc.stepConMutex.RUnlock()

	distance := bc.GetCurParam().GetCheckBlock().GetDistance() - 1

	for _, con := range stepConList {
		if effectiveHeight == con.StartHeight || (effectiveHeight > con.StartHeight && effectiveHeight-con.StartHeight < distance) ||
			(effectiveHeight < con.StartHeight && con.StartHeight-effectiveHeight < distance) {
			bc.log.Warnf("ignore consensus param due to new consensus should be out of distance")
			return false
		}
		continue
	}
	return true
}

// StepConListCheck 共识状态检查
func (bc *BaseClient) StepConListCheck(height uint64) {
	nextCon := bc.GetConByHeight(height)
	if nextCon == nil {
		bc.log.Debugf("no consensus need step...")
		return
	}

	currentConType := bc.curParam.Consensus.GetType()
	nextConType := nextCon.ConParam.GetType()

	fillConParam(nextCon.ConParam)
	bc.curParam.Consensus = nextCon.ConParam

	err := bc.stepNextCon(nextCon)
	if err != nil {
		panic(err.Error())
	}

	bc.log.Infof("consensus update from %s to %s successfully", currentConType, nextConType)
}

func (bc *BaseClient) stepNextCon(nextCon *StepConsensus) error {
	bc.Close()

	conType := nextCon.ConParam.GetType()
	nextCon.ConsensusImpl = NewConsensusModule(conType).(ConsensusModule)
	nextCon.ConsensusImpl.GetBaseClient().SetCurParam(bc.GetCurParam())
	nextCon.ConsensusImpl.GetBaseClient().GetCurParam().Consensus = nextCon.ConParam
	nextCon.ConsensusImpl.GetBaseClient().SetLastBlockSequence(bc.GetLastBlockSequence())
	nextCon.ConsensusImpl.GetBaseClient().SetLastBlockCheckedMessage(bc.GetLastBlockCheckedMessage())
	if bc.GetSequenceServer() != nil {
		nextCon.ConsensusImpl.GetBaseClient().SetSequenceServer(bc.GetSequenceServer())
		nextCon.ConsensusImpl.GetBaseClient().GetSequenceServer().SetBaseClient(nextCon.ConsensusImpl.GetBaseClient())
	}
	nextCon.ConsensusImpl.GetBaseClient().GetWorkerClient().Sub(types.ConsensusModule, mq.ModeWorker.String())
	nextCon.ConsensusImpl.GetBaseClient().SaveCurParam()
	nextCon.ConsensusImpl.Startup(bc.GetModuleCfg())
	return nil
}

func fillConParam(conParam *protosconfig.ConsensusParam) {
	switch conParam.GetType() {
	case protosconfig.DposConsensusType:
		dposPayload := conParam.GetDposPayload()
		if dposPayload.MinerCount == 0 {
			dposPayload.MinerCount = uint32(len(dposPayload.GetDefaultMiners()))
		}
		if dposPayload.MaxBlocksInTimeSlice == 0 {
			dposPayload.MaxBlocksInTimeSlice = common.DefaultMaxBlocksInTimeSlice
		}
		if dposPayload.TimeSliceLength == 0 {
			dposPayload.TimeSliceLength = common.DefaultTimeSliceLength
		}
		if dposPayload.RoundInterval == 0 {
			dposPayload.RoundInterval = common.DefaultRoundInterval
		}
	}
}
